﻿namespace Home.Security.Authentication.IdentityProviders
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Data.Entity;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web;
    using Home.Configuration;
    using Home.Http;
    using Home.Security.Authentication.Repository;
    using Newtonsoft.Json.Linq;

    internal class IdentityProviderQQ : IdentityProvider
    {
        // login - authorization code

        public override Task<string> GenerateLoginUriAsync()
        {
            var redirectUri = HttpUtility.UrlEncode($"http://{ConfigurationHelper.WebsiteAddress}/login-callback");
            var state = GenerateLoginState(AuthenticationType.WeChat);
            var result = $"https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id={ConfigurationHelper.IdentityProviderAppKeyQQ}&redirect_uri={redirectUri}&state={state}&scope=get_user_info";
            return Task.FromResult(result);
        }

        // access token

        public override Task<string> GenerateAccessTokenRequestUriAsync(AuthorizationCodeResult authorization)
        {
            var redirectUri = HttpUtility.UrlEncode($"http://{ConfigurationHelper.WebsiteAddress}/login-callback");
            var result = $"https://graph.qq.com/oauth2.0/token?client_id={ConfigurationHelper.IdentityProviderAppKeyQQ}&client_secret={ConfigurationHelper.IdentityProviderSecretQQ}&code={authorization.AuthorizationCode}&grant_type=authorization_code&redirect_uri={redirectUri}";
            return Task.FromResult(result);
        }

        public override async Task<AccessTokenResult> ParseAccessTokenResponseAsync(AuthorizationCodeResult authorization, IDictionary<string, string> values)
        {
            var accessToken = values["access_token"];
            if (accessToken == null)
            {
                var errorCode = default(string);
                var errorMessage = default(string);
                GetErrorInfo(values, out errorCode, out errorMessage);
                // TODO: log error message
                return null;
            }

            // get OpenID
            var openId = await GetOpenIdAsync(accessToken);
            if (string.IsNullOrEmpty(openId))
            {
                return null;
            }

            var refreshToken = values["refresh_token"];
            var expiresIn = values["expires_in"];

            var result = new AccessTokenResult(authorization.Id, authorization.TrackId, authorization.AuthenticationType)
            {
                AccessToken = accessToken,
                RefreshToken = refreshToken,
            };
            result.Properties.Add("access_token", accessToken);
            result.Properties.Add("refresh_token", refreshToken);
            result.Properties.Add("expires_in", expiresIn);
            result.Properties.Add("openid", openId);

            return result;
        }

        // refresh token

        public override Task<string> GenerateRefreshTokenRequestUriAsync(AuthenticationEntity entity)
        {
            var result = $"https://graph.qq.com/oauth2.0/token?client_id={ConfigurationHelper.IdentityProviderAppKeyWeChat}&client_secret={ConfigurationHelper.IdentityProviderSecretQQ}&grant_type=refresh_token&refresh_token={entity.RefreshToken}";
            return Task.FromResult(result);
        }

        public override Task<RefreshTokenResult> ParseRefreshTokenResponseAsync(AuthenticationEntity entity, IDictionary<string, string> values)
        {
            var accessToken = values["access_token"];
            if (accessToken == null)
            {
                var errorCode = default(string);
                var errorMessage = default(string);
                GetErrorInfo(values, out errorCode, out errorMessage);
                // TODO: log error message
                return null;
            }

            // get OpenID
            var openId = GetOpenId(entity);
            if (string.IsNullOrEmpty(openId))
            {
                return null;
            }

            var refreshToken = values["refresh_token"];
            var expiresIn = values["expires_in"];

            var result = new RefreshTokenResult(entity.Id, entity.TrackId, entity.AuthenticationType)
            {
                AccessToken = accessToken,
                RefreshToken = refreshToken,
            };
            result.Properties.Add("access_token", accessToken);
            result.Properties.Add("refresh_token", refreshToken);
            result.Properties.Add("expires_in", expiresIn);
            result.Properties.Add("openid", openId);

            return Task.FromResult(result);
        }

        // verify access token

        public override Task<string> GenerateVerifyAccessTokenRequestUriAsync(AuthenticationEntity entity)
        {
            var result = GenerateOpenIdRequestUri(entity.AccessToken);
            return Task.FromResult(result);
        }

        public override Task<VerifyAccessTokenResult> ParseVerifyAccessTokenResponseAsync(AuthenticationEntity entity, IDictionary<string, string> values)
        {
            var openId = GetOpenId(values);
            var result = new VerifyAccessTokenResult(entity.Id, entity.TrackId, entity.AuthenticationType)
            {
                IsValid = !string.IsNullOrEmpty(openId),
            };
            return Task.FromResult(result);
        }

        // user key

        public override Task<string> GetUserKeyAsync(AuthenticationEntity entity)
        {
            var result = GetOpenId(entity);
            return Task.FromResult(result);
        }

        // SNS

        public override async Task UpdateUserSnsAsync(ApplicationUserEntity entity, SnsResult sns)
        {
            var openId = sns.Properties["openid"];
            var photoUrl = sns.Properties["figureurl"];
            var unionId = sns.Properties["unionid"];
            entity.Key = openId;
            if (string.IsNullOrWhiteSpace(photoUrl))
            {
                entity.Photo = null;
            }
            else
            {
                // TODO: notice the performance here
                entity.Photo = await GetPhotoAsync(photoUrl);
            }
        }

        public override Task<string> GenerateSnsRequestUriAsync(AuthenticationEntity entity)
        {
            var openId = GetOpenId(entity);
            var result = $"https://graph.qq.com/user/get_user_info?access_token={entity.AccessToken}&oauth_consumer_key={ConfigurationHelper.IdentityProviderAppKeyQQ}&openid={openId}";
            return Task.FromResult(result);
        }

        public override Task<SnsResult> ParseSnsResponseAsync(AuthenticationEntity entity, IDictionary<string, string> values)
        {
            var result = new SnsResult();
            var ret = values["ret"];
            if (ret != "0")
            {
                return null;
            }

            result.Properties.TryAdd(values, "ret");
            result.Properties.TryAdd(values, "msg");
            result.Properties.TryAdd(values, "nickname");
            result.Properties.TryAdd(values, "figureurl");
            result.Properties.TryAdd(values, "figureurl_1");
            result.Properties.TryAdd(values, "figureurl_2");
            result.Properties.TryAdd(values, "figureurl_qq_1");
            result.Properties.TryAdd(values, "figureurl_qq_2");
            result.Properties.TryAdd(values, "gender");
            result.Properties.TryAdd(values, "is_yellow_vip");
            result.Properties.TryAdd(values, "vip");
            result.Properties.TryAdd(values, "yellow_vip_level");
            result.Properties.TryAdd(values, "level");
            result.Properties.TryAdd(values, "is_yellow_year_vip");
            return Task.FromResult(result);
        }

        // others

        public override IDictionary<string, string> ParseHttpResponse(string content)
        {
            if (string.IsNullOrEmpty(content))
            {
                content = string.Empty;
            }

            if (CheckHttpResponseJavaScriptCallbackJson(content))
            {
                // the format of some APIs is callback({"client_id": "...", "open_id": "..."});
                return ParseHttpResponseJavaScriptCallbackJson(content);
            }
            else if (CheckHttpResponseJson(content))
            {
                return ParseHttpResponseJson(content);
            }

            return ParseHttpResponseQueryString(content);
        }

        // helpers

        private static string GenerateOpenIdRequestUri(string accessToken)
        {
            var result = $"https://graph.qq.com/oauth2.0/me?access_token={accessToken}";
            return result;
        }

        private string ParseOpenIdResponse(string content)
        {
            if (string.IsNullOrEmpty(content))
            {
                return null;
            }

            var items = ParseHttpResponse(content);
            var result = GetOpenId(items);
            return result;
        }

        private async Task<string> GetOpenIdAsync(string accessToken)
        {
            var uri = GenerateOpenIdRequestUri(accessToken);
            var content = await HttpHelper.GetStringAsync(uri);
            if (content == null)
            {
                // TODO: add log here
                return null;
            }

            var result = ParseOpenIdResponse(content.Body);
            if (string.IsNullOrEmpty(result))
            {
                // TODO: add log here
                return null;
            }

            return result;
        }

        private static string GetOpenId(AuthenticationEntity entity)
        {
            var openId = default(string);
            if (entity.RefreshTokenProperties.ContainsKey("openid"))
            {
                openId = entity.RefreshTokenProperties["openid"];
            }
            else if (entity.AccessTokenProperties.ContainsKey("openid"))
            {
                openId = entity.AccessTokenProperties["openid"];
            }

            return string.IsNullOrEmpty(openId) ? null : openId;
        }

        private static string GetOpenId(IDictionary<string, string> values)
        {
            var openId = default(string);
            if (values.TryGetValue("openid", out openId))
            {
                return openId;
            }

            return null;
        }

        private static void GetErrorInfo(IDictionary<string, string> values, out string code, out string message)
        {
            code = values["code"];
            message = values["msg"];
        }
    }
}
